package com.whately.core.base.keygen;

import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * 分布式雪花ID算法
 * 数据中心和节点ID
 * 1 41       7        5    10
 * 1 时间戳  业务类型  机器码 序列
 */
public class SnowIdUtil {
    /**
     * 起始的时间戳
     * 2010-11-04 09:42:54
     */
    //private final static long twepoch = 1288834974657L;

    private final static long twepoch = ConstantsIdGenerator.epoch_20200101_ms;

    /**
     * 每一部分占用的位数
     */
    private final static long bizTypeIdBits = 7L;//128种业务类型
    private final static long workerIdBits = 5L;//最多32机器
    private final static long sequenceBits = 10L;

    /**
     * 每一部分的最大值
     */
    //2的幂datacenterIdBits ,-1
    public final static long maxBizTypeId = -1L ^ (-1L << bizTypeIdBits);
    //2的幂workerIdBits, -1
    public final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
    //1024
    private final static long maxSequence = -1L ^ (-1L << sequenceBits);

    /**
     * 每一部分向左的位移
     */
    private final static long workerIdShift = sequenceBits;
    private final static long bizTypeIdShift = sequenceBits + workerIdBits;
    private final static long timestampShift = sequenceBits + workerIdBits + bizTypeIdBits;

    private long bizTypeId; // 数据中心ID
    private long workerId; // 机器ID

    // 带时间的序列，原子操作
    private AtomicStampedReference<StampedSequence> current;

    //默认构造函数，只指定机器码，不指定数据中心（数据中心为0）
    public SnowIdUtil() {
        this(WhatelyMachineCodeHelper.getMachineCode(),0);
    }

    private SnowIdUtil(long workerId, long bizTypeId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(
                    String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (bizTypeId > maxBizTypeId || bizTypeId < 0) {
            throw new IllegalArgumentException(
                    String.format("bizTypeId Id can't be greater than %d or less than 0 or equal 0 [1-127] ", maxBizTypeId));
        }
        this.bizTypeId = bizTypeId;
        this.workerId = workerId;
        current = StampedSequence.initRefer();
    }

    public IdInfo nextIdModel() {
        StampedSequence seq = StampedSequence.nextSequence(current,maxSequence);
        IdInfo idInfo = new IdInfo(buildingId(seq),seq.getTs());
        return idInfo;
    }

    public long nextId() {
        StampedSequence seq = StampedSequence.nextSequence(current,maxSequence);
        return buildingId(seq);
    }

    private long buildingId(StampedSequence seq){
        return  (seq.getTs() - twepoch) << timestampShift // 时间戳部分
                | bizTypeId << bizTypeIdShift // 业务类型部分
                | workerId << workerIdShift // 机器标识部分
                | seq.getSequence(); // 序列号部分
    }

    /*******************************************************************************************************
     * 解析雪花id
     *******************************************************************************************************/

    /**
     * 以二进制位带-分隔符形式返回id
     *
     * @param snowId
     * @return
     */
    public static String analysis2BinaryStringBySnowId(long snowId) {

        char[] bId = Long.toBinaryString(snowId).toCharArray();
        StringBuilder sb = new StringBuilder();
        //标志位
        sb.append("0").append('-');

        //时间戳
        int timestamp = ((Long) (bId.length - ( bizTypeIdBits +workerIdBits + sequenceBits))).intValue();
        int zero = 41 - timestamp;
        //时间戳前面补全0
        for (int i = 0; i < zero; i++) {
            sb.append(0);
        }
        for (int i = 0; i < timestamp; i++) {
            sb.append(bId[i]);
        }
        sb.append('-');

        //biz type
        int timestampAndBizType = ((Long)(timestamp + bizTypeIdBits)).intValue();
        for (int i = timestamp; i < timestampAndBizType; i++) {
            sb.append(bId[i]);
        }
        sb.append('-');

        //work id
        for (int i = timestampAndBizType; i < timestamp +bizTypeIdBits+ workerIdBits; i++) {
            sb.append(bId[i]);
        }
        sb.append('-');

        //num seq
        int workLength = ((Long)(workerIdBits + bizTypeIdBits)).intValue();        //data center + work
        for (int i = timestamp + workLength; i < bId.length; i++) {
            sb.append(bId[i]);
        }
        return sb.toString();
    }


    /**
     * 根据雪花id输出真实时间戳，从19.。。
     *
     * @param snowId
     * @return
     */
    public static long analysis2TsMillisBySnowId(long snowId) {
        String bid = Long.toBinaryString(snowId);
        int timestamp = ((Long)(bid.length() - ( bizTypeIdBits +workerIdBits + sequenceBits))).intValue();
        String substring = bid.substring(0, timestamp);
        long tsInSnow=  Long.parseUnsignedLong(substring, 2);

        return tsInSnow + twepoch;
    }


    /**
     * 根据雪花id输出biz type
     *
     * @param snowId
     * @return
     */
    public static long analysis2BizTypeBySnowId(long snowId) {
        String bid = Long.toBinaryString(snowId);
        int timestamp = ((Long)(bid.length() - ( bizTypeIdBits +workerIdBits + sequenceBits))).intValue();
        int end = ((Long)(timestamp+ bizTypeIdBits)).intValue();
        String substring = bid.substring(timestamp, end);
        long inSnow=  Long.parseUnsignedLong(substring, 2);

        return inSnow;
    }

    /**
     * 根据雪花id输出biz type
     *
     * @param snowId
     * @return
     */
    public static long analysis2WorkIdBySnowId(long snowId) {
        String bid = Long.toBinaryString(snowId);
        int timestamp = ((Long)(bid.length() - ( bizTypeIdBits +workerIdBits + sequenceBits))).intValue();
        int begin = ((Long)(timestamp+ bizTypeIdBits)).intValue();
        int end = ((Long)(timestamp+ bizTypeIdBits+workerIdBits)).intValue();
        String substring = bid.substring(begin, end);
        long inSnow=  Long.parseUnsignedLong(substring, 2);

        return inSnow;
    }


    public static void main(String[] args) {

        SnowIdUtil u=new SnowIdUtil();


       for(int i =1 ;i<5;i++){
           long id = u.nextId();
           System.out.println(id);
           System.out.println(analysis2BinaryStringBySnowId(id));

       }


    }
}
